package com.github.llyb120.dsl

import cn.hutool.core.io.FileUtil

import java.nio.charset.StandardCharsets
import java.util.stream.Collectors

class DslParser {
    List<DslElement> elementList = new ArrayList<>();
    Map<String, DslElement> elementMap = new HashMap<>();
    static final String TAB2 = "\t\t";
    static final String TAB1 = "\t";
    static final String TAB3 = "\t\t\t";

    private Map<String, Set<String>> propertyDependList = new HashMap<>();

    void generate(String path, String packageName){
        File dir = new File(path);
        if(!dir.exists()){
            dir.mkdirs();
        }
        analyze()
//        String[] sp = className.split("\\.");
//        String packageName = sp[0..sp.length - 2].join(".")
        elementList.forEach({
            def classFile = generateElementCode(packageName, it);
            FileUtil.writeString(classFile.getCode(), dir.getAbsolutePath() + "/" + classFile.getName(), StandardCharsets.UTF_8)
        })
        def classFile = generateParserCode(packageName)
        FileUtil.writeString(classFile.getCode(), dir.getAbsolutePath() + "/" + classFile.getName(), StandardCharsets.UTF_8)
    }

    /**
     * 分析依赖关系
     */
    void analyze(){
        elementList.forEach({
            elementMap.put(it.getName(), it)
        })
        Map<String, Integer> countMap = new HashMap<>()
        elementList.forEach({
            countMap.clear()
            it.getPropertyList().forEach(p -> {
                if(p.getType().equals(Object.class)){
                    def element = elementMap.get(p.getName())
                    if (element != null) {
                        element.getDependList().add(it.getName())
                    }
                }
                if(p.getMultiple()){
                    it.getMultiplePropertyList().add(p.getName())
                }
                int num = countMap.getOrDefault(p.getName(), 0)
                if(num >= 1){
                    it.getMultiplePropertyList().add(p.getName())
                }
                countMap.put(p.getName(), num + 1)


                //添加属性的依赖关系
                Set set = propertyDependList.getOrDefault(p.getName(), [])
                set.add(it.getName())
                propertyDependList.put(p.getName(), set)
            })
        })
    }

    ClassFile generateParserCode(String packageName){
        StringJoiner sj = new StringJoiner("\n")
        sj.add("""package ${packageName}""")
        sj.add("")
        sj.add("class All{")
        sj.add("    private final static ThreadLocal<LinkedList> local = new ThreadLocal<LinkedList>(){")
        sj.add("        @Override")
        sj.add("        protected LinkedList initialValue() {")
        sj.add("            return new LinkedList()")
        sj.add("        }")
        sj.add("    }")
        sj.add("")
        //定义element
        elementList.forEach({
            sj.add("    static ${toName(it.getName())} ${it.getName()}(Closure closure){")
            sj.add("        def _${it.getName()} = new ${toName(it.getName())}()")
            if(!it.getDependList().isEmpty()){
                sj.add("        def item = pickStack()")
                it.getDependList().forEach(d -> {
                    sj.add("        if(item instanceof ${toName(d)}){")
                    sj.add("            ${setAttribute(d, "item", it.getName(), "_" + it.getName())}")
                    sj.add("        }")
                })
            }
            sj.add("        pushStack(_${it.getName()})")
            sj.add("        closure.call()")
            if(!it.getRoot()){
                sj.add("        popStack()")
            }
            sj.add("        return _${it.getName()}")
            sj.add("    }")
            sj.add("")
            //定义构造器
            if(!it.getConstructors().isEmpty()){
                it.getConstructors().forEach(constructor -> {
                    def str = (constructor as List<String>).stream()
                        .map(p -> {
                            return it.getTypeName(p) + " " + p
                        })
                        .collect(Collectors.joining(","))
                    //copy up
                    sj.add("    static ${toName(it.getName())} ${it.getName()}(${str},Closure closure = null){")
                    sj.add("        def _${it.getName()} = new ${toName(it.getName())}()")
                    Arrays.asList(constructor).forEach(cons -> {
                        sj.add("        _${it.getName()}.set${toName(cons)}(${cons})")
                    })
                    if(!it.getDependList().isEmpty()){
                        sj.add("        def item = pickStack()")
                        it.getDependList().forEach(d -> {
                            sj.add("        if(item instanceof ${toName(d)}){")
                            sj.add("            ${setAttribute(d, "item", it.getName(), "_" + it.getName())}")
                            sj.add("        }")
                        })
                    }
                    sj.add("        pushStack(_${it.getName()})")
                    sj.add("        if(closure != null){")
                    sj.add("            closure.call()")
                    sj.add("        }")
                    if(!it.getRoot()){
                        sj.add("        popStack()")
                    }
                    sj.add("        return _${it.getName()}")
                    sj.add("    }")
                    sj.add("")
                })

            }
        })

        //定义属性
        Set<String> defined = []
        elementList.forEach({
            it.getPropertyList().forEach(p -> {
                if(elementMap.containsKey(p.getName()) && p.getType().equals(Object.class)){
//                    //已经定义过的结构不再重复定义
                    return
                }
                String key = p.getName() + p.getType().getSimpleName()
                if(defined.contains(key)){
                    return
                }
                defined.add(key)
                sj.add("    static void ${p.getName()}(${p.getType().getSimpleName()} val){")
                sj.add("        def item = pickStack()")
                propertyDependList.get(p.getName()).forEach(d -> {
                    def element = elementMap.get(d)
                    if (element == null) {
                        return
                    }
                    sj.add("        if(item instanceof ${toName(d)}){")
                    sj.add("            ${setAttribute(d, "item", p.getName(), "val")}")
                    sj.add("        }")
                })
                sj.add("    }")
                sj.add("")
            })
        })
        //定义工具方法
        sj.add("    private final static Object pickStack(){")
        sj.add("        return local.get().getLast()")
        sj.add("    }")
        sj.add("    private final static Object pushStack(obj){")
        sj.add("        local.get().addLast(obj)")
        sj.add("    }")
        sj.add("    private final static Object popStack(){")
        sj.add("        return local.get().removeLast()")
        sj.add("    }")
        sj.add("    public final static List getElements(){")
        sj.add("        return (List) local.get()")
        sj.add("    }")
        sj.add("}")
        return new ClassFile(
                name: "All.groovy",
                code: sj.toString()
        )
    }

    ClassFile generateElementCode(String packageName, DslElement dslElement){
        StringJoiner sj = new StringJoiner("\n");
        sj.add("""package ${packageName}""");
        sj.add("");
        sj.add("""class ${toName(dslElement.getName())} {""");
        //查找出现多个的属性
        Map<String, Short> cache = new HashMap<>();
        dslElement.getPropertyList().forEach({
            short num = cache.getOrDefault(it.getName(), (short)0) + 1
            cache.put(it.getName(), num);
        })
        dslElement.getPropertyList().forEach({
            if(!cache.containsKey(it.getName())){
                return
            }
            if(cache.get(it.getName()) > 1){
                //多种情况下必定为list
                sj.add("""${TAB1}List<Object> ${it.getName()} = []""");
                cache.remove(it.getName())
            } else if(it.getType().equals(Object.class)){
                if(it.getMultiple()){
                    sj.add("""${TAB1}List<${toName(it.getName())}> ${it.getName()} = []""");
                } else {
                    sj.add(buildProperty(TAB1, toName(it.getName()), it.getName()))
//                    sj.add("""${TAB1}${toName(it.getName())} ${it.getName()}""");
                }
            } else {
                if(it.getMultiple()){
                    sj.add("""${TAB1}List<${it.getType().getSimpleName()}> ${it.getName()} = []""");
                } else {
                    sj.add(buildProperty(TAB1, it.getType().getSimpleName(), it.getName()));
//                    sj.add("""${TAB1}${it.getType().getSimpleName()} ${it.getName()}""");
                }
            }
        })
        sj.add("");
        sj.add("}");
        return new ClassFile(
                name: toName(dslElement.getName()) + ".groovy",
                code: sj.toString()
        )
    }

    String buildProperty(String tabs, String type, String name){
        switch (type){
            case "Map":
                return "${tabs}Map ${name} = [:]";
            default:
                return "${tabs}${type} ${name}";
        }
    }

    String toName(String str){
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    String setAttribute(String elementName, String obj, String key, String value){
        DslElement element = elementMap.get(elementName)
        if (element == null) {
            assert false
        }
        if(element.getMultiplePropertyList().contains(key)) {
            return "${obj}.get${toName(key)}().add(${value})"
        } else {
            return "${obj}.set${toName(key)}(${value})"
        }
    }
}
